import com.magi.builder.*;
import com.magi.config.BuildingConfig;
import com.magi.core.AppUtils;
import com.magi.core.DatabaseOperator;
import com.magi.core.TableMetaData;
import com.magi.exception.TypeMappingNotFoundException;
import org.dom4j.DocumentException;

import java.io.FileWriter;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Application {
    private static Parameter currentParam;
    private static int currentNeededCount = -1;
    private static final BuildingConfig config = new BuildingConfig() ;
    private enum Parameter {
        TABLE_NAME      ("-t", 1, false ),
        DATABASE_URL    ("-u", 1, true  ),
        OUTPUT_PATH     ("-o", 1, false ),
        ROOT_PACKAGE    ("-p", 1, true  ),
        USERNAME        ("-U", 1, false ),
        PASSWORD        ("-P", 1, false )
        ;

        private final String name;
        private final int neededCount;
        private final boolean isNecessary;

        Parameter(String name, int neededCount, boolean isNecessary) {
            this.name = name;
            this.neededCount = neededCount;
            this.isNecessary = isNecessary;
        }

        public String getName() {
            return name;
        }

        public int getNeededCount() {
            return neededCount;
        }

        public boolean isNecessary() {
            return isNecessary;
        }

        public static Parameter paramOf(String parmaName) {
            for (Parameter value : Parameter.values()) {
                if (value.getName().equals(parmaName)) {
                    return value;
                }
            }
            return null;
        }
    }

    private static class Param {
        private Parameter parameter;
        private final List<String> args = new ArrayList<>();

        Param() {}

        public void setParameter(Parameter parameter) {
            this.parameter = parameter;
        }

        public void addArg(String arg) {
            this.args.add(arg);
        }

        public Parameter getParameter() {
            return parameter;
        }

        public List<String> getArgs() {
            return args;
        }
    }

    public static void main(String[] args) throws SQLException, IOException, TypeMappingNotFoundException, DocumentException {
        Map<Parameter, Param> paramMap = new HashMap<>();

        if (args.length <= 0) {
            printHelp();
            exit();
        }

        for (String arg : args) {
            Parameter parameter = Parameter.paramOf(arg);
            if (parameter != null) {
                if (currentParam != null && currentParam.isNecessary() && currentNeededCount > 0) {
                    printUnsatisfiedArgCount(currentParam);
                    exit(-1);
                }
                Param param = new Param();
                param.setParameter(parameter);
                paramMap.put(parameter, param);
                currentParam = parameter;
                currentNeededCount = parameter.getNeededCount();
            } else {
                if (currentParam == null || currentNeededCount <= 0) {
                    System.out.println("Unknown identifier '" + arg + "'.");
                    exit(-1);
                }
                currentNeededCount -= 1;
                paramMap.get(currentParam).addArg(arg);
            }
        }
        if (currentNeededCount > 0) {
            printUnsatisfiedArgCount(currentParam);
            exit(-1);
        }
        run(handleParams(paramMap));
    }

    private static void printHelp() {
        System.out.println("TODO HELP");
    }

    private static void printUnsatisfiedArgCount(Parameter p) {
        System.out.printf("Param '%s' needed %s args but only %s was given.",
                p.getName(),
                p.getNeededCount(),
                p.getNeededCount() - currentNeededCount);
        System.out.println();
    }

    private static BuildingConfig handleParams(Map<Parameter, Param> paramMap) {
        for (Parameter value : Parameter.values()) {
            if (value.isNecessary()) {
                if (paramMap.getOrDefault(value, null) == null) {
                    System.out.printf("Unsatisfied parma needed '%s'", value.getName());
                    exit(-1);
                }

            }
        }
        for (Param value : paramMap.values()) {
            handleParam(value);
        }
        return config;
    }

    private static void exit() {
        System.exit(0);
    }

    private static void exit(int code) {
        System.exit(code);
    }

    private static void run(BuildingConfig config) throws SQLException, IOException, TypeMappingNotFoundException, DocumentException {
        DatabaseOperator databaseOperator = new DatabaseOperator(config);
        String selectedTable = config.getSelectedTable();
        if (selectedTable != null) {
            TableMetaData metaData = databaseOperator.getTableMateData(selectedTable);
            generateAllFile(metaData, config.getOutputPath());
        } else {
            for (TableMetaData metaData : databaseOperator.getAllTableMateData()) {
                generateAllFile(metaData, config.getOutputPath());
            }
        }
        System.out.println("All generating has done, please check the output in \"" + config.getOutputPath() + "\".");

    }

    private static void generateAllFile(TableMetaData metaData, String path) throws IOException, TypeMappingNotFoundException, DocumentException {
        generateJavaMapper(metaData, path);
        generateJavaPojo(metaData, path);
        generateXmlMapper(metaData, path);
    }

    private static void generateJavaMapper(TableMetaData metaData, String path) throws IOException {
        IJavaMapperBuilder builder = new JavaMapperBuilder(config);
        FileWriter writer = new FileWriter(path + "\\" + AppUtils.getMapperName(metaData) + ".java");
        System.out.printf("Generating java mapper file for table '%s'...", metaData.getTableName());
        System.out.println();
        writer.write(builder.Build(metaData));
        writer.close();
    }

    private static void generateJavaPojo(TableMetaData metaData, String path) throws IOException, DocumentException, TypeMappingNotFoundException {
        IJavaPojoBuilder builder = new JavaPojoBuilder(config);
        FileWriter writer = new FileWriter(path + "\\" + AppUtils.getPojoName(metaData) + ".java");
        System.out.printf("Generating java pojo file for table '%s'...", metaData.getTableName());
        System.out.println();
        writer.write(builder.Build(metaData));
        writer.close();
    }

    private static void generateXmlMapper(TableMetaData metaData, String path) throws IOException {
        IXmlSQLMapperBuilder builder = new XmlSQLMapperBuilder(config);
        FileWriter writer = new FileWriter(path + "\\" + AppUtils.getMapperName(metaData) + ".xml");
        System.out.printf("Generating xml mapper file for table '%s'...", metaData.getTableName());
        System.out.println();
        writer.write(builder.Build(metaData));
        writer.close();
    }

    private static void handleParam(Param p) {
        switch (p.getParameter()) {
            case DATABASE_URL:
                config.setDatabaseURL(p.getArgs().get(0));
                break;
            case TABLE_NAME:
                config.setSelectedTable(p.getArgs().get(0));
                break;
            case PASSWORD:
                config.setPassword(p.getArgs().get(0));
                break;
            case USERNAME:
                config.setUsername(p.getArgs().get(0));
                break;
            case OUTPUT_PATH:
                config.setOutputPath(p.getArgs().get(0));
                break;
            case ROOT_PACKAGE:
                config.setRootPackageName(p.getArgs().get(0));
                break;
            default:
                System.out.println("Unhandled parameter '" + p.getParameter().getName() + "'.");
                exit(-2);
        }
    }
}
